/*************************************************************************
 *
 *   file		: AuraCollection.cs
 *   copyright		: (C) The WCell Team
 *   email		: info@wcell.org
 *   last changed	: $LastChangedDate: 2009-09-13 19:09:14 +0100 (Sun, 13 Sep 2009) $
 *   last author	: $LastChangedBy: dominikseifert $
 *   revision		: $Rev: 1095 $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using WCell.Constants.Spells;
using WCell.Constants.Updates;
using WCell.RealmServer.Entities;

namespace WCell.RealmServer.Spells.Auras
{
	/// <summary>
	/// Represents the collection of all Auras of a Unit
	/// TODO: Uniqueness of Auras?
	/// </summary>
	public class AuraCollection
	{
		private static Logger log = LogManager.GetCurrentClassLogger();

		protected Unit m_owner;
		protected Dictionary<AuraIndex, Aura> m_auras;

		/// <summary>
		/// An immutable array that contains all Auras and is re-created
		/// whenever an Aura is added or removed (for faster iteration during Updating).
		/// </summary>
		protected Aura[] m_AuraArray;

		/// <summary>
		/// All non-passive Auras.		
		/// Through items and racial abilities, one Unit can easily have 100 Auras active at a time -		
		/// No need to iterate over all of them when checking for interruption etc.		
		/// </summary>        
		private readonly Aura[] m_visibleAuras = new Aura[64];

		private int m_visAuraCount;


		public AuraCollection(Unit owner)
		{
			m_auras = new Dictionary<AuraIndex, Aura>();
			m_AuraArray = Aura.EmptyArray;

			m_owner = owner;
		}

		public Aura[] VisibleAuras
		{
			get { return m_visibleAuras; }
		}

		public int VisibleAuraCount
		{
			get { return m_visAuraCount; }
		}

		public Aura[] ActiveAuras
		{
			get { return m_AuraArray; }
		}

		public Unit Owner
		{
			get
			{
				return m_owner;
			}
			internal set
			{
				m_owner = value;
			}
		}

		public int Count
		{
			get
			{
				return m_auras.Count;
			}
		}

		/// <summary>
		/// Called by Character ctor
		/// </summary>
		internal void PlayerInitialize()
		{
			// In order to re-create the aura correctly, we need to save:
			// casterInfo
			// hideFromClient
			// Tricky: Stacked AuraEffectHandlers?
			//for (UnitFields field = AuraHandler.FirstPositiveField; field < AuraHandler.LastNegativeField; field++) {
			//    uint spellId = owner.GetUInt32(field);
			//    if (spellId != 0) {
			//        List<AuraEffectHandler> handlers = new List<AuraEffectHandler>();

			//        Aura aura = new Aura(this, new CasterInfo(EntityId.Zero, 1), SpellHandler.ById[spellId], false, handlers, false);
			//    }
			//}
			Clear();
		}

		#region Add & Get
		public Aura this[SpellId spellId, bool positive]
		{
			get
			{
				var spell = SpellHandler.Get(spellId);
				if (spell != null)
				{
					return this[spell, positive];
				}
				return null;
			}
		}

		public Aura this[Spell spell, bool positive]
		{
			get
			{
				Aura aura;
				m_auras.TryGetValue(new AuraIndex(spell.AuraUID, positive), out aura);
				if (aura != null && aura.Spell.Id == spell.Id)
				{
					return aura;
				}
				return null;
			}
		}

		public Aura this[AuraIndex auraId]
		{
			get
			{
				Aura aura;
				m_auras.TryGetValue(auraId, out aura);
				return aura;
			}
		}

		/// <summary>
		/// Returns the first visible (not passive) Aura with the given Type (if any).
		/// </summary>
		/// <remarks>Requires region context.</remarks>
		/// <param name="type"></param>
		/// <returns></returns>
		public Aura this[AuraType type]
		{
			get
			{
				foreach (var aura in m_visibleAuras)
				{
					if (aura != null && aura.Spell.HasEffectWith(effect => effect.AuraType == type))
					{
						return aura;
					}
				}
				return null;
			}
		}

		/// <summary>
		/// Returns the first visible Aura with the given SpellId
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Aura this[SpellId id]
		{
			get
			{
				foreach (var aura in m_AuraArray)
				{
					if (aura.Spell.SpellId == id)
					{
						return aura;
					}
				}
				return null;
			}
		}

		public bool Contains(SpellId id)
		{
			foreach (var aura in m_AuraArray)
			{
				if (aura.Spell.SpellId == id)
				{
					return true;
				}
			}
			return false;
		}

		public bool Contains(uint id)
		{
			foreach (var aura in m_AuraArray)
			{
				if (aura.Spell.Id == id)
				{
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Iterates over all Auras and returns the n'th visible one
		/// </summary>
		/// <returns>The nth visible Aura or null if there is none</returns>
		public Aura GetAt(uint n)
		{
			if (n < m_visibleAuras.Length)
			{
				return m_visibleAuras[n];
			}
			return null;
		}

		/// <summary>
		/// Applies the given spell as a buff or debuff (the owner being the caster).
		/// Also initializes the new Aura.
		/// </summary>
		/// <returns>null if Spell is not an Aura</returns>
		public Aura AddSelf(SpellId id, bool noTimeout)
		{
			return Add(m_owner.CasterInfo, SpellHandler.Get(id), noTimeout);
		}

		/// <summary>
		/// Applies the given spell as a buff or debuff (the owner being the caster).
		/// Also initializes the new Aura.
		/// </summary>
		/// <returns>null if Spell is not an Aura</returns>
		public Aura AddSelf(Spell spell, bool noTimeout)
		{
			return Add(m_owner.CasterInfo, spell, noTimeout);
		}

		/// <summary>
		/// Applies the given spell as a buff or debuff.
		/// Also initializes the new Aura.
		/// </summary>
		/// <returns>null if Spell is not an Aura</returns>
		public Aura Add(CasterInfo caster, Spell spell, bool noTimeout)
		{
			var handlers = AuraHandler.CreateEffectHandlers(spell.AuraEffects, caster.Caster, m_owner, caster.Level);
			if (handlers != null)
			{
				var aura = Add(caster, spell, handlers);
				if (aura != null)
				{
					aura.Start(null, noTimeout);
				}
				return aura;
			}
			return null;
		}

		/// <summary>
		/// Adds a new Aura with the given information to the Owner. 
		/// Does not initialize the new Aura.
		/// If you use this method, make sure to call <c>Initialize(ITickTimer)</c> on the newly created Aura.
		/// Overrides any existing Aura that matches.
		/// </summary>
		/// <returns>null if Spell is not an Aura</returns>
		public Aura Add(CasterInfo caster, Spell spell, IList<AuraEffectHandler> handlers)
		{
			var beneficial = true;
			var amplitude = 0;
			foreach (var handler in handlers)
			{
				if (handler.SpellEffect.HarmType == HarmType.Harmful)
				{
					beneficial = false;
				}

				// Aura has the Amplitude of the first effect with Amplitude set
				if (amplitude == 0 && (int)handler.SpellEffect.Amplitude > 0)
				{
					amplitude = (int)handler.SpellEffect.Amplitude;
				}
			}

			var id = new AuraIndex
			{
				AuraUID = spell.AuraUID,
				IsPositive = beneficial
			};

			var aura = this[id];
			if (aura != null)
			{
				aura.TryRemove(false);
				if (aura.IsActive)
				{
					// could not override (because its triggered by a surrounding AreaAura)
					return null;
				}
			}

			// create new Aura
			// Get an index for the aura
			var index = beneficial ? GetFreePositiveIndex() : GetFreeNegativeIndex();

			if (index == 0xFF)
			{
				// no more free fields
				return null;
			}

			aura = new Aura(this, caster, spell, handlers, index, beneficial, amplitude);
			m_auras.Add(id, aura);
			if (!aura.Spell.IsPassive)
			{
				m_visibleAuras[index] = aura;
				++m_visAuraCount;
			}
			InvalidateAurasCopy();
			return aura;
		}

		/// <summary>
		/// Called when an Aura gets added or removed
		/// </summary>
		/// <param name="aura"></param>
		internal void OnAuraChange(Aura aura)
		{
			if (aura.IsBeneficial && aura.Spell.HasModifierEffects)
			{
				ReApplyAffectedAuras(aura.Spell);
			}
		}

		/// <summary>
		/// Reapplies all passive permanent Auras that are affected by the given Spell
		/// </summary>
		/// <param name="spell"></param>
		public void ReApplyAffectedAuras(Spell spell)
		{
			foreach (var aura in m_AuraArray)
			{
				if (aura.Spell.IsPassive &&
					!aura.HasTimeout &&
					aura.Spell != spell &&
					aura.Spell.MatchesMask(spell.AllAffectingMasks))
				{
					aura.ReApplyEffects();
				}
			}
		}

		/// <summary>
		/// Adds an already created Aura (eg for SpellSteal)
		/// </summary>
		public void Add(Aura aura)
		{
			if (m_owner.IsInWorld)
			{
				var id = aura.Id;

				if (CheckStackOrOverride(aura.CasterInfo, id, aura.Spell))
				{
					m_auras.Add(id, aura);
					if (!aura.Spell.IsPassive)
					{
						//m_nonPassiveAuras.Add(aura);
					}
					InvalidateAurasCopy();
				}
			}
		}

		/// <summary>
		/// Trying to apply a new Aura:
		/// Stack if stackable, else remove any Aura that conflicts with the new one.
		/// </summary>
		/// <returns>Whether or not another Aura may be applied</returns>
		internal bool CheckStackOrOverride(CasterInfo caster, AuraIndex id, Spell spell)
		{
			var oldAura = this[id];
			if (oldAura != null)
			{
				if ((!oldAura.Spell.CanStack || oldAura.Spell != spell))
				{
					// cancel previously existing Aura
					oldAura.TryRemove(true);
				}
				else
				{
					// stack aura
					oldAura.Stack(caster, spell);
					return false;
				}
			}
			else
			{
				// no aura found
				// check for per-caster-restrictions
				if (spell.AuraCasterGroup != null)
				{
					var count = 0;
					foreach (var aura in m_AuraArray)
					{
						if (aura.CasterInfo.CasterId == caster.CasterId && spell.AuraCasterGroup.Contains(aura.Spell))
						{
							count++;
							if (count >= spell.AuraCasterGroup.MaxCount)
							{
								return false;
							}
						}
					}
				}
			}
			return true;
		}

		/// <summary>
		/// Create a new Copy of 
		/// </summary>
		private void InvalidateAurasCopy()
		{
			//if (m_auras.Count > m_auras.Count)
			m_AuraArray = m_auras.Values.ToArray();
		}

		/// <summary>
		/// Returns whether there are any harmful Auras on the Unit.
		/// Unit cannot leave combat mode while under the influence of harmful Auras.
		/// </summary>
		/// <returns></returns>
		public bool HasHarmfulAura()
		{
			return FindFirst(aura => !aura.IsBeneficial) != null;
		}

		/// <summary>
		/// Returns the first Aura that matches the given Predicate.
		/// Only looks in active Auras.
		/// </summary>
		/// <remarks>Requires region context.</remarks>
		public Aura FindFirst(Predicate<Aura> condition)
		{
			//foreach (Aura aura in m_nonPassiveAuras)
			foreach (var aura in m_visibleAuras)
			{
				if (aura != null && condition(aura))
				{
					return aura;
				}
			}
			return null;
		}
		#endregion

		#region Remove
		/// <summary>
		/// Removes all Auras that match the given predicate
		/// </summary>
		/// <param name="predicate"></param>
		public void RemoveWhere(Predicate<Aura> predicate)
		{
			//Aura[] auras = m_nonPassiveAuras.ToArray();
			var auras = m_visibleAuras;
			foreach (var aura in auras)
			{
				if (aura != null && predicate(aura))
				{
					aura.Remove(true);
				}

			}
		}

		/// <summary>
		/// Removes the first occurance of an Aura that matches the given predicate
		/// </summary>
		/// <param name="predicate"></param>
		public void RemoveFirstWhere(Predicate<Aura> predicate)
		{
			//Aura[] auras = m_nonPassiveAuras.ToArray();
			var auras = m_visibleAuras;
			foreach (Aura aura in auras)
			{
				if (aura != null && predicate(aura))
				{
					aura.Remove(true);
					break;
				}
			}
		}

		/// <summary>
		/// Removes auras based on their interrupt flag.
		/// </summary>
		/// <param name="interruptFlag">the interrupt flags to remove the auras by</param>
		public void RemoveByFlag(AuraInterruptFlags interruptFlag)
		{
			//Aura[] auras = m_nonPassiveAuras.ToArray();
			foreach (var aura in m_visibleAuras)
			{
				if (aura != null && (aura.Spell.AuraInterruptFlags & interruptFlag) != 0)
				{
					aura.Remove(true);
				}
			}
		}

		public bool Cancel(uint auraUID, bool positive)
		{
			var id = new AuraIndex { AuraUID = auraUID, IsPositive = positive };
			return Cancel(id);
		}

		public bool Cancel(AuraIndex auraId)
		{
			Aura aura;
			if (m_auras.TryGetValue(auraId, out aura))
			{
				aura.Cancel();
				return true;
			}
			return false;
		}

		public bool Cancel(SpellId id)
		{
			var spell = SpellHandler.Get(id);
			if (spell != null)
			{
				return Cancel(spell);
			}
			return false;
		}

		/// <summary>
		/// Removes and cancels the first Aura of the given Spell
		/// </summary>
		public bool Cancel(Spell spell)
		{
			Aura aura;
			if (spell.HasBeneficialEffects)
			{
				aura = this[spell, true];
			}
			else
			{
				aura = null;
			}

			if (aura == null && spell.HasHarmfulEffects)
			{
				aura = this[spell, false];
			}

			if (aura != null && aura.Spell.Id == spell.Id)
			{
				aura.Cancel();
				return true;
			}
			return false;
		}

		/// <summary>
		/// Removes the given Aura without cancelling it.
		/// Automatically called by <see cref="Aura.Remove"/>.
		/// </summary>
		public void Cancel(Aura aura)
		{
			m_auras.Remove(aura.Id);
			if (aura.Spell.IsProc)
			{
				m_owner.RemoveProcHandler(aura);
			}
			if (!aura.Spell.IsPassive)
			{
				//m_nonPassiveAuras.Remove(aura);
				m_visibleAuras[aura.Index] = null;
				--m_visAuraCount;
			}
			InvalidateAurasCopy();
			OnAuraChange(aura);
		}

		/// <summary>
		/// Removes all active auras
		/// </summary>
		public void RemoveActiveAuras()
		{
			//foreach (var aura in m_nonPassiveAuras.ToArray())
			for (var i = 0; i < m_visibleAuras.Length; i++)
			{
				var aura = m_visibleAuras[i];
				if (aura != null)
					aura.Remove(true);
			}
		}

		/// <summary>
		/// Removes all auras that are casted by anyone but myself
		/// </summary>
		public void RemoveOthersAuras()
		{
			for (var i = 0; i < m_visibleAuras.Length; i++)
			{
				var aura = m_visibleAuras[i];
				if (aura != null && aura.Caster != m_owner)
				{
					aura.Remove(true);
				}
			}
		}

		/// <summary>
		/// Removes all auras, including passive auras -
		/// Not recommended, since passive Auras might also be casted by Items etc.
		/// </summary>
		public void Clear()
		{
			var auras = m_AuraArray;
			foreach (var aura in auras)
			{
				aura.Remove(true);
			}
		}

		#endregion

		#region Aura Fields
		/// <summary>
		/// TODO: Improve by having a container for recyclable ids
		/// </summary>
		/// <returns></returns>
		public byte GetFreePositiveIndex()
		{
			for (byte i = 0; i < m_visibleAuras.Length - 16; i++)
			{
				if (m_visibleAuras[i] == null)
					return i;
			}
			return 0xFF;
		}

		public byte GetFreeNegativeIndex()
		{
			for (byte i = 48; i < m_visibleAuras.Length; i++)
			{
				if (m_visibleAuras[i] == null)
					return i;
			}
			return 0xFF;
		}
		#endregion

		#region Special Auras
		/// <summary>
		/// Always represents your curent ride (or null when not mounted)
		/// </summary>
		public Aura MountAura
		{
			get;
			internal set;
		}

		/// <summary>
		/// Represents the Aura that makes us a Ghost
		/// </summary>
		public Aura GhostAura
		{
			get;
			internal set;
		}
		#endregion

		public bool HasStrongerAura(Spell spell, WorldObject caster)
		{
			var positive = caster != null ? !caster.MayAttack(m_owner) : true;
			var existingAura = m_owner.Auras[new AuraIndex(spell.AuraUID, positive)];
			if (existingAura != null && existingAura.Spell.IsStrongerThan(spell))
			{
				//failedReason = SpellFailedReason.AuraBounced;
				return true;
			}
			return false;
		}

		public IEnumerator<Aura> GetEnumerator()
		{
			if (m_auras.Count == 0)
			{
				return Aura.EmptyEnumerator;
			}
			return _GetEnumerator();
		}

		/// <summary>
		/// We need a second method because yield return and return statements cannot
		/// co-exist in one method.
		/// </summary>
		/// <returns></returns>
		IEnumerator<Aura> _GetEnumerator()
		{
			for (var i = 0; i < m_AuraArray.Length; i++)
			{
				yield return m_AuraArray[i];
			}
		}

		/// <summary>
		/// Dumps all currently applied auras to the given chr
		/// </summary>
		/// <param name="chr"></param>
		/// <param name="includePassive">Whether to also include invisible effects (eg through items etc)</param>
		public void DumpTo(Character chr, bool includePassive)
		{
			if (m_auras.Count > 0)
			{
				chr.SendSystemMessage("####################");
				chr.SendSystemMessage("{0}'s Auras:", m_owner);
				foreach (var aura in m_auras.Values)
				{
					if (includePassive || !aura.Spell.IsPassive)
					{
						chr.SendSystemMessage("	{0}", aura);
					}
				}
			}
			else
			{
				chr.SendSystemMessage("{0} has no active Auras.", m_owner);
			}
		}
	}
}
